盒子
盒子
文章目录
  1. 前言
  2. 原理分析
    1. Mark-Sweep 算法
    2. Mark-Compact 算法
    3. Copying 算法
    4. 各算法时间比较
  3. 参考文献

为什么 Major GC 比 Minor GC 慢很多?

前言

前几天面试的时候有面试官问我:为什么 Major GC 比 Minor GC 慢很多?

原因如下:

  • 新生代内存空间比老年代小。
  • 新生代和老年代采用的垃圾收集算法不同,即新生代采用的 Copying 算法比老年代采用的 Mark-Sweep 算法耗时更短。

Major GC, Minor GC, 新生代, 老年代的概念请看:http://xiazdong.me/2015/01/03/jvm-memory-model/

原理分析

  • 新生代一般采用 Copying 算法进行垃圾收集。
  • 老年代一般采用 Mark-Sweep 或 Mark-Compact 或两者组合的方式(定期进行 Compact)进行垃圾收集。

Mark-Sweep 算法

这个算法共有两个步骤:

  • Mark: 从 GC Roots 开始遍历可达的所有节点,并对其标记。因此时间复杂度为存活对象的个数。
  • Sweep: 对整个老年代内存进行遍历,并将没标记的内存块进行清理。

伪代码如下:

for(Object obj : reachable objects)
mark(obj)
for(Object obj : old generation)
if(obj is not marked)
sweep(obj)

Mark-Sweep 算法的优点是简单方便,缺点是造成内存碎片。

Mark-Compact 算法

由于 Mark-Sweep 算法会导致内存碎片化,因此就引入了 Mark-Compact 算法,该算法执行完毕后,存活的对象会按序放置,但是执行时间较长。

Mark-Compact 是 时间换空间,Copying 是 空间换时间,他和 Copying 算法最后都是将存活的对象整理,但是 Mark-Compact 由于要在原有内存上做 Compact(空间更小),因此耗费的时间比 Copying 时间更长(时间更长)。

这个算法共两个步骤:

  • Mark: 从 GC Roots 开始遍历可达的所有节点,并对其标记。因此时间复杂度为存活对象的个数。
  • Compact: 对整个老年代内存进行遍历,并将没标记的内存块进行清理。

伪代码如下:

for(Object obj : reachable objects)
mark(obj)
for(Object obj : old generation)
if(obj is not marked)
compact(obj)

Copying 算法

Copying 算法是新生代采用的垃圾收集算法,他将新生代内存分成两块(或多块),每次只使用其中的一块。因此浪费了一倍的内存空间,但是由于新生代的内存空间相比老年代较小,因此浪费一倍也还好。

该算法就是 Mark-Sweep 算法的 Mark 阶段:遍历一遍 GC Roots 可达的所有节点(假设这些节点都在块1),但是在遍历节点的同时,将该节点按序复制到新生代的另一块内存(块2)。当遍历完所有可达节点后,一次性将块1的内存清理干净。

伪代码如下:

for(Object obj : reachable objects) # reachable objects is in block 1
mark(obj)
copy obj to block 2
sweep block 1

Copying 算法的复杂度依赖于存活对象的个数,如果存活的对象太多,那么复制操作会占用很长时间,但是由于新生代具有存活对象很少的特点,因此该算法适用于新生代的垃圾收集。

Copying 算法的优点是一次遍历即可完成,缺点是如果存活对象很多,那么复制操作会很耗时。

各算法时间比较

上面介绍了 Mark-Sweep, Mark-Compact, Copying 三种算法,总体来说可以归纳为:

  • Mark-Sweep = Mark 操作 + Sweep 操作。
  • Mark-Compact = Mark 操作 + Compact 操作。
  • Copying = Mark 操作 + Copy 操作。

这些操作的耗时比较:

  • Compact > Copy > Mark > Sweep
  • Mark + Sweep > Copy

参考文献

支持一下
扫一扫,支持xiazdong